/**
 * @author: mattwang@tencent.com
 * @date: 2012-10-20
 */

#ifndef __SVD_MODEL_H__
#define __SVD_MODEL_H__

#include <cstdio>
#include <cstdlib>
#include "apex-tensor/apex_tensor.h"
#include "type_def.h"
#include "param_def.h"

struct SvdModelParam {
	int num_user;
	int num_item;
	int num_factor;
	int num_global;

	float u_init_sigma;
	float i_init_sigma;
	float base_score;
	int no_user_bias;
	int num_ufeedback;
	float ufeedback_init_sigma;
	int num_randinit_ufactor;
	int num_randinit_ifactor;
	int common_latent_space;

	SvdModelParam() {
		num_user = num_item = num_global = num_factor = 0;
		u_init_sigma = i_init_sigma = 0.01f;
		no_user_bias = 0;
		base_score = 0.5f;
		num_ufeedback = 0;
		ufeedback_init_sigma = 0.0f;
		num_randinit_ufactor = num_randinit_ifactor = 0;
		common_latent_space = 0;
	}
};

struct SvdModel {
	enum FUNCTION {
		LINEAR = 0, SIGMOID_L2 = 1,
		/*! \brief sigmoid function, log-likelihood loss */
		SIGMOID_LIKELIHOOD = 2,
		/*!
		 * \brief sigmoid function, log-likelihood loss. this option is reserved for rank
		 *   during prediction, the model will ouput the result
		 *   before sigmoid transformation, which is sufficient for rank
		 */
		SIGMOID_RANK = 3,
		/*! \brief smoothed hinge loss, used in MMMF */
		HINGE_SMOOTH = 5
	};

	int space_allocated;
	SvdModelParam param;
	apex_tensor::CTensor1D ui_bias;
	apex_tensor::CTensor2D W_uiset;
	apex_tensor::CTensor1D u_bias;
	apex_tensor::CTensor2D W_user;
	apex_tensor::CTensor1D i_bias;
	apex_tensor::CTensor2D W_item;
	apex_tensor::CTensor1D g_bias;
	apex_tensor::CTensor1D ufeedback_bias;
	apex_tensor::CTensor2D W_ufeedback;
	SvdModel() {
		space_allocated = 0;
	}
	inline void alloc_space() {
		{ // allocate space for user/item factor
			if (param.common_latent_space == 0) {
				ui_bias.set_param(param.num_user + param.num_item);
				W_uiset.set_param(param.num_user + param.num_item, param.num_factor);
			} else {
				assert(param.num_user == param.num_item);
				ui_bias.set_param(param.num_item);
				W_uiset.set_param(param.num_item, param.num_factor);
			}
			apex_tensor::tensor::alloc_space(ui_bias);
			apex_tensor::tensor::alloc_space(W_uiset);
			if (param.common_latent_space == 0) {
				u_bias = ui_bias.sub_area(0, param.num_user);
				W_user = W_uiset.sub_area(0, 0, param.num_user, param.num_factor);
				i_bias = ui_bias.sub_area(param.num_user, param.num_item);
				W_item = W_uiset.sub_area(param.num_user, 0, param.num_item, param.num_factor);
			} else {
				W_user = W_uiset;
				W_item = W_uiset;
				u_bias = ui_bias;
				i_bias = ui_bias;
			}
		}
		{
			g_bias.set_param(param.num_global);
			apex_tensor::tensor::alloc_space(g_bias);
		}
		ufeedback_bias.set_param(param.num_ufeedback);
		W_ufeedback.set_param(param.num_ufeedback, param.num_factor);
		apex_tensor::tensor::alloc_space(ufeedback_bias);
		apex_tensor::tensor::alloc_space(W_ufeedback);
		space_allocated = 1;
	}
	inline void free_space(void) {
		if (space_allocated == 0)
			return;
		apex_tensor::tensor::free_space(ui_bias);
		apex_tensor::tensor::free_space(W_uiset);
		apex_tensor::tensor::free_space(g_bias);

		apex_tensor::tensor::free_space(ufeedback_bias);
		apex_tensor::tensor::free_space(W_ufeedback);
		space_allocated = 0;
	}
	inline void load_from_file(FILE *fi) {
		if (fread(&param, sizeof(SvdModelParam), 1, fi) == 0) {
			printf("error loading CF SVD model\n");
			exit(-1);
		}
		if (space_allocated != 0)
			this->free_space();
		this->alloc_space();
		{ // handle for common latent space, a bit complex for compatible issue
			if (param.common_latent_space == 0) {
				apex_tensor::cpu_only::load_from_file(u_bias, fi, true);
				apex_tensor::cpu_only::load_from_file(W_user, fi, true);
				apex_tensor::cpu_only::load_from_file(i_bias, fi, true);
				apex_tensor::cpu_only::load_from_file(W_item, fi, true);
			} else {
				apex_tensor::cpu_only::load_from_file(ui_bias, fi, true);
				apex_tensor::cpu_only::load_from_file(W_uiset, fi, true);
			}
		}
		{
			apex_tensor::cpu_only::load_from_file(g_bias, fi, true);
		}
		apex_tensor::cpu_only::load_from_file(ufeedback_bias, fi, true);
		apex_tensor::cpu_only::load_from_file(W_ufeedback, fi, true);
		space_allocated = 1;
	}
	inline void save_to_file(FILE *fo) const {
		fwrite(&param, sizeof(SvdModelParam), 1, fo);
		{ // handle for common user/item latent space, make it compatible with previous format
			if (param.common_latent_space == 0) {
				apex_tensor::cpu_only::save_to_file(u_bias, fo);
				apex_tensor::cpu_only::save_to_file(W_user, fo);
				apex_tensor::cpu_only::save_to_file(i_bias, fo);
				apex_tensor::cpu_only::save_to_file(W_item, fo);
			} else {
				apex_tensor::cpu_only::save_to_file(ui_bias, fo);
				apex_tensor::cpu_only::save_to_file(W_uiset, fo);
			}
		}
		{
			apex_tensor::cpu_only::save_to_file(g_bias, fo);
		}
		apex_tensor::cpu_only::save_to_file(ufeedback_bias, fo);
		apex_tensor::cpu_only::save_to_file(W_ufeedback, fo);
	}
	inline void rand_init() {
		ui_bias = 0.0f;
		g_bias = 0.0f;
// scale base score by inverse of activation function
		switch (svd_param.function_type) {
		case LINEAR:
		case HINGE_SMOOTH:
			break;
		case SIGMOID_L2:
		case SIGMOID_LIKELIHOOD:
		case SIGMOID_RANK: {
			assert(param.base_score > 0.0f && param.base_score < 1.0f);
			param.base_score = -logf(1.0f / param.base_score - 1.0f);
			break;
		}
		default:
			info("unkown function type");
		}

		{ // initialize ufactor
			apex_tensor::CTensor2D W_uinit;
			if (param.num_randinit_ufactor != 0) {
				W_uinit = W_user.sub_area(0, 0, param.num_randinit_ufactor, W_user.x_max);
			} else {
				W_uinit = W_user;
			}
			apex_tensor::tensor::sample_gaussian(W_uinit, param.u_init_sigma);
		}

		// only need to initialize once in common latent space
		if (param.common_latent_space == 0) {
			// initialize ifactor
			apex_tensor::CTensor2D W_iinit;
			if (param.num_randinit_ifactor != 0) {
				W_iinit = W_item.sub_area(0, 0, param.num_randinit_ifactor, W_item.x_max);
			} else {
				W_iinit = W_item;
			}
			apex_tensor::tensor::sample_gaussian(W_iinit, param.i_init_sigma);
		}

		ufeedback_bias = 0.0f;
		apex_tensor::tensor::sample_gaussian(W_ufeedback, param.ufeedback_init_sigma);
	}
};

#endif

